package redis

import (
	"errors"
	"github.com/go-redis/redis"
	"math"
	"strconv"
	"time"
)

//本项目使用简化版的投票函数
//投一票就加432分    86400/200张赞成票就可以给帖子续一天

/*投票的几种情况：
1. direction=1时，有两种情况：
	1. 之前没有投过票，现在改投赞成票  --->更新分数和投票情况 差值的绝对值：1  +432
	2. 之前投反对票，现在投赞成票  --->更新分数和投票情况 差值的绝对值：2  +432*2
2. direction=0时，有两种情况：
	1. 之前投过反对票，现在取消投票  --->更新分数和投票情况 差值的绝对值：1  +432
	2. 之前投过赞成票，现在取消投票  --->更新分数和投票情况 差值的绝对值：1  -432
3. direction=-1时，有两种情况：
	1. 之前按没有投过票，现在投反对票  --->更新分数和投票情况 差值的绝对值：1  -432
	2. 之前投赞成票，现在改投反对票  --->更新分数和投票情况 差值的绝对值：2  -432*2

上面的情况可以分为两类
	1. 第一点的所有和第二点第一条的情况是：现在的分数大于原来的分数
	2. 第二点的第二条和第三点的所有的情况是：现在的分数小于原来的分数
*/

const (
	oneWeekInSeconds         = 7 * 24 * 3600
	scorePerVote     float64 = 432 //一天有24*60*60=86400s，一天需要投票200个票，投一票就加432分，86400/200张赞成票就可以给帖子续一天
)

var (
	ErrVoteTimeExpire = errors.New("投票时间已过！")
	ErrVoteRepeated   = errors.New("不允许重复投票！")
)

// 在redis中创建帖子
func CreatePost(postID, communitID int64) error {
	//添加事务操作，同时创建两个rediskey，不成功就返回
	pipeline := client.TxPipeline()
	//帖子时间
	pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: postID,
	})

	//帖子分数，Score也是跟着创建帖子的时间来，因为投票多一个，就留存432s，所以初始score都是时间
	pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: postID,
	})

	//把帖子id加到社区set里
	cKey := getRedisKey(KeyCommunitySetPF + strconv.Itoa(int(communitID)))
	pipeline.SAdd(cKey, postID)
	_, err := pipeline.Exec()

	return err
}

// 为帖子投票
func VoteForPost(userID, postID string, value float64) error {
	//1. 判读投票限制
	// 取redis取帖子发表时间
	postTime := client.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val()
	if float64(time.Now().Unix())-postTime > oneWeekInSeconds {
		return ErrVoteTimeExpire
	}

	//2. 更新帖子的分数
	//先查当前用户给当前帖子的投票纪录
	ov := client.ZScore(getRedisKey(KeyPostVotedZSetPF+postID), userID).Val()
	if value == ov {
		//这一次投票的值和之前保存的值一致，则不允许重复投票
		return ErrVoteRepeated
	}

	var op float64
	if value > ov {
		//现在的投票状态大于原来的投票状态
		op = 1 //代表是分类一，做加操作
	} else {
		//现在的投票状态小于原来的投票状态
		op = -1 //代表是分类一，做减操作
	}
	//计算两次投票状态的差值，投票状态-1，0，1
	diff := math.Abs(ov - value)

	//计算投票和第三步需要放到一个pipeline事务中操作
	pipeline := client.TxPipeline()
	pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, postID)

	//3. 记录用户为该帖子投票的数据
	if value == 0 {
		pipeline.ZRem(getRedisKey(KeyPostVotedZSetPF+postID), userID)
	} else {
		pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPF+postID), redis.Z{
			Score:  value, //赞成还是反对票
			Member: userID,
		})
	}
	_, err := pipeline.Exec()
	return err
}
